﻿using System;
using System.Collections.Generic;
using GACore.Helpers;
using GACore.Progress;
using GACore.Structures;

namespace GACore
{
	public abstract class Problem
	{
		protected Vector[] vectors;
		private MarriageHelper marriageHelper;
		private ReplacementHelper replacementHelper;
		protected Preferences preferences;
		protected Random random = new Random();

		public HistoryEntry Init(Preferences preferences)
		{
			this.preferences = preferences;
			this.vectors = new Vector[preferences.PopulationCount];
			this.marriageHelper = new MarriageHelper(preferences.MarriageProperties);
			this.replacementHelper = new ReplacementHelper(preferences.ReplacementProperties);
			this.computeFitness = GetComputeFitnessFunction();
			this.vectors = CreateInitialPopulation();
			ComputeFitness(ref vectors);
			return new HistoryEntry(vectors);
		}

		private Problem.ComputeFitnessDelegate GetComputeFitnessFunction()
		{
			switch (preferences.FitnessProperties.FitnessFunctionType)
			{
				case GACore.Structures.FitnessFunctionType.EuklidDistance:
					return new ComputeFitnessDelegate(ComputeFitnessEuklidDistance);
				case GACore.Structures.FitnessFunctionType.HammingDistance:
					return new ComputeFitnessDelegate(ComputeFitnessHammingDistance);
				case GACore.Structures.FitnessFunctionType.Count:
					return new ComputeFitnessDelegate(ComputeFitnessCount);
				case GACore.Structures.FitnessFunctionType.Sum:
					return new ComputeFitnessDelegate(ComputeFitnessSum);
				case GACore.Structures.FitnessFunctionType.Individual:
					return null;
				//	return new ComputeFitnessDelegate(ComputeFitnessIndividual);
				default:
					throw new ArgumentException("Bitte geben Sie einen definierten Fitnessfunktionstyp an.");
			}
		}

		protected abstract Vector[] CreateInitialPopulation();

		public HistoryEntry ComputeStep()
		{
			Vector[] childVectors = DoCrossover();
			DoMutation(ref childVectors);
			ComputeFitness(ref childVectors);
			ReplaceGenerations(childVectors);

			return new HistoryEntry(new List<Vector>(this.vectors).ToArray());
		}

		private Vector[] DoCrossover()
		{
			return marriageHelper.DoCrossover(vectors);
		}

		private void DoMutation(ref Vector[] vectors)
		{
			switch (preferences.MutationProperties.MutationScope)
			{
				case MutationScope.Population:
					MutatePopulation(ref vectors);
					break;
				case MutationScope.Vector:
					for (int i = 0; i < vectors.Length; i++)
					{
						vectors[i].MutateVector(preferences.MutationProperties);
					}
					break;
				default:
					break;
			}
		}

		private void MutatePopulation(ref Vector[] vectors)
		{
			int count = (int)(preferences.MutationProperties.MutationRate * vectors.Length * vectors[0].Length);
			for (int i = 0; i < count; i++)
			{
				int element = random.Next(vectors.Length * vectors[0].Length);
				vectors[element / vectors[0].Length].MutateElement(element % vectors[0].Length, preferences.MutationProperties);
			}
		}

		private void ReplaceGenerations(Vector[] childVectors)
		{
			ComputeFitness(ref this.vectors);
			this.vectors = replacementHelper.Replace(this.vectors, childVectors);
		}

		protected virtual void ComputeFitness(ref Vector[] vectors)
		{
			Array.Sort(vectors);
		}

		private delegate double ComputeFitnessDelegate(Vector vector);
		private ComputeFitnessDelegate computeFitness;

		private double ComputeFitnessSum(Vector vector)
		{
			return vector.ComputeFitnessSum();
		}

		private double ComputeFitnessCount(Vector vector)
		{
			return vector.ComputeFitnessCount(preferences.FitnessProperties.ObjectToCount);
		}

		private double ComputeFitnessEuklidDistance(Vector vector)
		{
			return vector.ComputeFitnessEuklidDistance(preferences.FitnessProperties.CompareVector);
		}

		private double ComputeFitnessHammingDistance(Vector vector)
		{
			return vector.ComputeFitnessHammingDistance(preferences.FitnessProperties.CompareVector);
		}

		//protected abstract double ComputeFitnessIndividual(Vector vector);

		public abstract string GetProblemName();

		public abstract string GetProblemRepresentation();
	}
}
